今回はJavaFXにおける照明とカメラを見ていく。JavaFXでは照明とカメラのクラスは種類が多くなく、実質的に利用するクラスはそれぞれ1つだけである。
■ 照明
現在(2015年10月27日)、JavaFXで利用可能な照明クラスは以下の2つである。
クラス |
内容 |
PointLight |
3Dシーン内の点光源(全方位性)を表す |
AmbientLight |
環境光(シーン全体の明るさ)を表す |
■ 照明クラスのシーンへの適用
照明クラスは他の3Dオブジェクト同様、シーンノードに追加するだけでシーンに適用される。照明が影響するオブジェクトをしたい場合は、getScope関数で対象オブジェクトを指定する。光の色は指定できる(setColor関数を利用)が、光の強さは指定できない。
また、注意点としては、照明本体はレンダリングされないことである。このことは、サンプルにて確認する。
■ PhongMaterialにおける表面色の計算
3Dオブジェクト表面の色は、照明とマテリアルによって決定される。現在、JavaFXにて適用可能なマテリアルはPhongMaterialクラスのみである。PhongMaterialクラスにおいて、3Dオブジェクト表面色を計算する方法は以下の通りである。
for each ambient light source i {
ambient += lightColor[i]
}
for each point light source i {
diffuse += (L[i] . N) * lightColor[i]
specular += ((R[i] . V) ^ (specularPower * intensity(specularMap))) * lightColor[i]
}
color = (ambient + diffuse) * diffuseColor * diffuseMap
+ specular * specularColor * specularMap
+ selfIlluminationMap
変数 |
内容 |
lightColor[i] |
光源iの色 |
L[i] |
表面から光源iへのベクトル |
N |
表面の法線ベクトル |
R[i] |
表面法線を中心としたL[i]の正規化反射ベクトル |
V |
正規化ビュー・ベクトル |
※『PhongMaterial』の実装(『JavaDoc - クラスPhongMaterial』より引用)
拡散反射光の実装が独特で、数式を見るとL[i]は正規化(ベクトルの大きさ=1)されていない模様。光源と面の法線の内積に比例して拡散反射光が大きくなるため、同じ入射角であれば光源が遠いほど明るくなるという実装になっているようだ。L[i]が正規化されている場合を考えても距離による光の減衰は考慮されていないようで、光源からの距離よりも入射角が重要視されているようだ。
また、『影(shadow)=自オブジェクトにより暗くなる部分』は表現されるが、『陰(shade)=他オブジェクトにより暗くなる部分』は表現されない。総じて見るとJavaFXでは『リアルな映像表現』を目指しているのではなく、『図説などで利用する簡単な3D表現』を目指しているように感じられる。
■ カメラ
JavaFXで利用可能なカメラは以下の通りである。ParallelCameraクラスについては、JavaFXがUIコントロールの描写で利用するために用意されているらしく、利用する場面はないと思われる。
クラス |
内容 |
PerspectiveCamera |
シーンをレンダリングするための透視投影カメラ |
ParallelCamera |
透視投影補正を使用せずにシーンをレンダリング |
■ カメラの利用方法
カメラクラスはシーングラフへの追加だけでなく、Scene.setCamea関数でシーンにも設定する必要があるため、注意が必要である。
PerspectiveCameraクラスでは、視錐台内の3Dオブジェクトを描写する。視錐台の手前の面への距離をsetNearClip関数で、奥の面への距離をsetFarClip関数で設定する。視錐台の横幅は、setFieldOfView関数で視野角を用いて設定する。
■ サンプルプログラム
照明とカメラの動作を確認するサンプルコードを以下に示す。 サンプルでは2つの直方体と、照明を可視化するための大きな球(透明度10%)が描画されている。
◇サンプルコード
package application_fx;
import javafx.application.Application;
import javafx.scene.AmbientLight;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.LightBase;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Box;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TestLight extends Application {
public static void main(String[] args)
{ launch( args );}
@Override
public void start(Stage primaryStage) throws Exception
{
// シーングラフのルートを作成
Group root = new Group();
// 地面を作成
Box ground = new Box();
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor( Color.web( "9FCC7F" ) );
ground.setWidth( 500 );
ground.setHeight( 0.5 );
ground.setDepth( 500 );
ground.setMaterial( material );
root.getChildren().add( ground );
// ボックス(近景)を作成
Box box = new Box();
box.setWidth( 3 );
box.setHeight( 5 );
box.setDepth( 3 );
box.getTransforms().add( new Translate( 0 , 0 , 0 ) );
root.getChildren().add( box );
// ボックス2(遠景)を作成
Box box2 = new Box();
box2.setWidth( 3 );
box2.setHeight( 300 );
box2.setDepth( 3 );
box2.getTransforms().add( new Translate( 10.0 , 0.0 , 250.0 ) );
root.getChildren().add( box2 );
// 空気遠近法を表す層を作成
root.getChildren().add( createAir() );
// カメラを設定
Camera camera = new PerspectiveCamera( true );
camera.setFarClip( 1000 );
camera.getTransforms().add( new Translate( 3.5 , -3.5 , -20 ) );
root.getChildren().add( camera );
// 照明を設定
LightBase light = new PointLight();
light.getTransforms().add( new Translate( 50.0 , -50.0 , 165.0 ) );
root.getChildren().add( light );
// アンビエント光
final AmbientLight ambientLight = new AmbientLight( Color.rgb(50,50,50) );
root.getChildren().add(ambientLight);
// 空のグラデーションを作成
Stop[] stops = new Stop[]{ new Stop( 0 , Color.WHITE ),
new Stop( 0.5, Color.web("71afec") ),
new Stop( 1 , Color.web("2454a0") ) };
LinearGradient sky = new LinearGradient( 0 ,400 , 0 , 0 , false , CycleMethod.NO_CYCLE , stops );
// 3D用のーンを作成
Scene scene = new Scene( root , 600 , 400 , true , SceneAntialiasing.BALANCED );
scene.setFill( sky );
scene.setCamera( camera );
// ウィンドウ表示
primaryStage.setScene( scene );
primaryStage.show();
}
/**
* 空気遠近法を表現するための球を作成
* @return
*/
public Node createAir()
{
// ルートノードを作成
Group root = new Group();
// 半透明のマテリアルを作成
PhongMaterial dMaterial = new PhongMaterial();
dMaterial.setDiffuseColor( Color.web("FFFFFF",0.1) );
//dMaterial.setBumpMap( new Image( new File("img/bumpMap0.png").toURI().toString() ) );
// 空気遠近法の奥行き層1を作成
Sphere air1 = new Sphere( 500 );
air1.setCullFace( CullFace.FRONT );
air1.setMaterial( dMaterial );
root.getChildren().add( air1 );
// 奥行きの層2を作成
Sphere air2 = new Sphere( 200 );
air2.setCullFace( CullFace.FRONT );
air2.setMaterial( dMaterial );
root.getChildren().add( air2 );
// 奥行きの層3を作成
Sphere air3 = new Sphere( 175 );
air3.setCullFace( CullFace.FRONT );
air3.setMaterial( dMaterial );
root.getChildren().add( air3 );
return root;
}
}
◇実行結果
◇解説
カメラの設定は65行目~68行目と88行目で設定している。視錐台の奥までの距離を1000に設定し、3Dオブジェクトが切れないように設定している。カメラの移動は3Dオブジェクトの移動と同様、getTransforms().add関数でカメラオブジェクトに座標変換を設定している。
照明(PointLight)は71行目~73行目で設定している。実行結果の画像右上に照明を設置している。サンプルでは、ぼんやりと光っている様子が見えるが、これは可視化するために透明度の高いオブジェクトを配置している(62行目)ためで、このオブジェクトを配置しない場合の実行結果は以下の通りになる。照明自体が描画されないことが確認できる。 また、実行結果の画像左下のオブジェクトに注目すると、陰ができていないことが確認できる。
環境光(AmbientLight)は76行目~77行目で設定している。環境光は、画面全体の明るさを調整するものである。上記サンプルから、環境光を抜いた結果は以下の画像のようになる。
■ 参照
- JavaDoc - クラスPhongMaterial
- JavaDoc - クラスLightBase
- JavaDoc - クラスCamera
1. 誤字と思われる部分がありましたので、ご報告いたします。
ひじょうにありがたく思っております。
誤字と思われる部分がありましたので、ご報告いたします。
(気づいたので、ご報告するレベルです)
http://krr.blog.shinobi.jp/javafx/javafx%203d%20%E7%85%A7%E6%98%8E%E3%83%BB%E3%82%AB%E3%83%A1%E3%83%A9
JavaFX 3D 照明・カメラ
のページです。
■カメラ
の説明部分で
「花井」とありますが「はない」かと思われます。
修正は必須とは思いませんが、よければ修正ください。
よろしくお願いいたします。
Re:誤字と思われる部分がありましたので、ご報告いたします。
誤字、修正させていただきました